#include "game.h"

Note w_notas[] = {{250.0, 5}, {300.0, 5}, {350.0, 5}, {400.0, 5},{450.0, 5}, {500.0, 5}, {550.0, 5}, {600.0, 5}, {650.0, 5}, {700.0, 5}}; 
Song win_s = { sizeof(w_notas)/sizeof(Note), 1, w_notas};

Note go_notas[] = {{700.0, 5}, {650.0, 5}, {600.0, 5}, {550.0, 5},{500.0, 5}, {450.0, 5}, {400.0, 5}, {350.0, 5}, {300.0, 5}, {250.0, 5}};
Song go_s = { sizeof(go_notas)/sizeof(Note), 1, go_notas};

Note n[] = {{2000.0, 1}};
Song shot_s = { sizeof(n)/sizeof(Note), 0, n};

Sprite *game_logo;		//sprites to the game over logo and the game logo that is displayed in the menu
Sprite *menu_s[3];			//Array with the sprites that are indicating the menu options


/** Initializes the game variables like the struct level
 *  fields and the boy and fire sprites
 */
void game_init()
{
	level.seconds = 70 + 5*level.level;
	level.destroyed_balls = 0;
	level.nballs = level.level*MIN_BALLS;
	level.maxballs = level.nballs*7;
	level.balls = malloc(level.maxballs*sizeof(Sprite*));
	fill_balls();
	fire = create_sprite(fire_xpm);
	boy = create_sprite(shoot_xpm);
	boy->x = 20; 
	boy->y = VRES - boy->height;
	draw_sprite(boy, base);
}

/** Free the allocated spaced to the sprites
 *  and to the array of balls in the level
 */
void game_finalize()
{
	free(level.balls);
	delete_sprite(fire, base);
	destroy_sprite(fire);
	delete_sprite(boy, base);
	destroy_sprite(boy);
}

/** Draw a sprite, play the win song and
 *  increase the integer level of the 
 *  struct level
 */
void change_level()
{
	Sprite* win = create_sprite(win_xpm);
	win->x = (HRES/2) - win->width/2;
	win->y = (VRES/2) - win->height;
	clear_screen(0,base);
	draw_sprite(win,base);
	drawStringAt("LEVEL CLEARED", win->x - 180, win->y + win->height + 64, 15, 0, 4, base, table);
	play_song(&win_s);
	delete_sprite(win,base);
	destroy_sprite(win);
	game_finalize();
	level.level++;                       //increments the level to the next one
	clear_screen(0,base);
	game_init();
}

/** Fill the balls array in the struct level with the
 *  number of balls corresponding to the level
 */
void fill_balls()
{
	int i;
	for(i = 0; i < level.nballs; i++)
	{
		level.balls[i] = create_sprite(blue_ball4_xpm);
		level.balls[i]->x = 200+i*150; level.balls[i]->y = 200;        //changes de position of the ball created
		level.balls[i]->xspeed = 1; level.balls[i]->yspeed = -1;
	}
}

//Simulate the balls movement
void move_balls(char* base)
{
	int i;
	for(i = 0; i < level.nballs; i++)
	{
		if(level.balls[i] != NULL)
		{
			level.balls[i]->yspeed += 1;
			move_sprite(level.balls[i], level.balls[i]->xspeed, level.balls[i]->yspeed, base);
		}
	}
}

/** If the ball in the array isn't the one of the smallest 
 * destoys it and creates 2 smaller balls. if the ball with that index
 * is one of the smallest it is only destroyed. The score
 * and the number of destroyed balls are increased
 */
void divide_balls(int index)
{	
	score+= level.seconds;
	//biggest ball
	if(level.balls[index]->height == 40)
	{
		//create first new ball and initialize it
		level.balls[level.nballs] = create_sprite(blue_ball3_xpm);
		level.balls[level.nballs]->xspeed = level.balls[index]->xspeed;
		level.balls[level.nballs]->yspeed = level.balls[index]->yspeed;
		level.balls[level.nballs]->x = level.balls[index]->x - level.balls[level.nballs]->width;
		level.balls[level.nballs]->y = level.balls[index]->y;
		level.nballs++;
		//create second new ball and initialize it
		level.balls[level.nballs] = create_sprite(blue_ball3_xpm);
		level.balls[level.nballs]->xspeed = -level.balls[index]->xspeed;
		level.balls[level.nballs]->yspeed = level.balls[index]->yspeed;
		level.balls[level.nballs]->x = level.balls[index]->x + level.balls[level.nballs]->width;;
		level.balls[level.nballs]->y = level.balls[index]->y;
		level.nballs++;
	}
	//intermediate ball
	if(level.balls[index]->height == 26)
	{
		//create first new ball and initialize it
		level.balls[level.nballs] = create_sprite(blue_ball2_xpm);
		level.balls[level.nballs]->xspeed = level.balls[index]->xspeed;
		level.balls[level.nballs]->yspeed = level.balls[index]->yspeed;
		level.balls[level.nballs]->x = level.balls[index]->x - level.balls[level.nballs]->width;;
		level.balls[level.nballs]->y = level.balls[index]->y;
		level.nballs++;
		//create second new ball and initialize it
		level.balls[level.nballs] = create_sprite(blue_ball2_xpm);
		level.balls[level.nballs]->xspeed = -level.balls[index]->xspeed;
		level.balls[level.nballs]->yspeed = level.balls[index]->yspeed;
		level.balls[level.nballs]->x = level.balls[index]->x + level.balls[level.nballs]->width;;
		level.balls[level.nballs]->y = level.balls[index]->y;
		level.nballs++;
	}
	level.destroyed_balls++;
	delete_sprite(level.balls[index], base);
	destroy_sprite(level.balls[index]);
	level.balls[index] = NULL;
	
}

/** Checks if the shot collide with any ball
 *  If there is a collision true is returned
 *  else false is returned
 */
Bool check_fire_collision()
{
	int i;
	for(i = 0; i < level.nballs; i++)
	{
		if(level.balls[i] != NULL)
		{   //verifies if the fire sprite is inside of a ball area
			if((fire->y >= level.balls[i]->y && fire->y <= level.balls[i]->y + level.balls[i]->height)
				|| (fire->y + fire->height >= level.balls[i]->y && fire->y+fire->height <= level.balls[i]->y + level.balls[i]->height)) 
				if((fire->x >= level.balls[i]->x && fire->x <= level.balls[i]->x + level.balls[i]->width)
				 || (fire->x + fire->width >= level.balls[i]->x && fire->x+fire->width <= level.balls[i]->x + level.balls[i]->width))
				{
					divide_balls(i);
					return true;
				}
		}
	}
	return false;
}

/** Checks if the any ball collides with the boy
 *  If there is a collision true is returned
 *  else false is returned
 */
Bool check_boy_collision()
{
	int i;
	for(i = 0; i < level.nballs; i++)
	{
		if(level.balls[i] != NULL)
		{   //verifies if the boy sprite is inside of a ball area
			if(level.balls[i]->y +level.balls[i]->height >= boy->y 
			&& ((level.balls[i]->x >= boy->x && level.balls[i]->x <= boy->x + boy->width)
			|| ((level.balls[i]->x+level.balls[i]->width >= boy->x && level.balls[i]->x+level.balls[i]->width <= boy->x + boy->width))))
				{
					return true;
				}
		}
	}
	return false;
}

/** Centers the sprite 'f' on the top of the sprite 's'
 *  and changes the speed of the firts sprite
 */
void shot(Sprite* s, Sprite* f, char* base)
{
	
	f->x = s->x + (s->width / 2);
	f->y = s->y - f->height - 1;
	f->yspeed = -10;

}

/** Check if the mouse is "inside" any sprite
 *  and processes the click of the left button
 */
void check_menu_option()
{
	int i;
	for(i = 0; i < 3; i++)
	{
			if(y >= menu_s[i]->y && y <= menu_s[i]->y + menu_s[i]->height && 
				x >= menu_s[i]->x && x <= menu_s[i]->x + menu_s[i]->width)
				{
					//if it is on the game over sprite and the left button
					//is pressed the game ends
					if(i == 1 && left_b == 1)
					{
							exit(0);
					}
					//if it is on the new game sprite and the left button
					//is pressed the game is initialized
					else if(i == 0 && left_b == 1)
					{
							game_on = true;
							clear_screen(0,base);
							game_init();
							return;
					}
			        //if it is on the help sprite and the left button
					//is pressed the menu changes to the help menu
					else if(i == 2 && left_b == 1)
					{
							help = true;
							clear_screen(0,base);
							return;
					}
				}
	}
	return;
}


/**
 * does the processment of the pressed keys
 */
void process_kbd()
{
		int last_sc = 0;
		if(!queueEmpty(apontQueue))
		{
			//obtains the next key to be processed
			last_sc = queueGet(apontQueue);
			//processing the keys during game
			if(game_on)
			{
				if(pause)
				{
					if(last_sc == 0x19) //if 'p' is pressed
						pause = false;
				}
				else
					switch(last_sc)
					{	
						//'ESC' ends the game				
						case 1:
							game_on = false;
							break;
						//'Q' ends the game	
						case 0x10:
							game_on = false;
							break;
							
						//boy movement to the left
						case 0x1e:
							move_sprite(boy, -boy_move, 0, base);
							break;
						//boy movement to the right	
						case 0x20:
							move_sprite(boy, boy_move, 0, base);
							break;
						//when 'space' is pressed activates the shot event
						case 0x39:
							event |= FIRE_EVENT;
							break;
						//when 'P' is pressed the game is paused
						case 0x19:
							pause = true;
							break;
						
						default:
							break;
					}
			}
			//processing the keys in the menu
			else
			{
				if(!help)
					switch(last_sc)
					{
						//'ESC' ends the program
						case 1:
							exit(0);
							break;
							
						//'Q' ends the program
						case 0x10:
							exit(0);
							break;
							
						//'N' initiliaze the game	
						case 0x31:
							game_on = true;
							clear_screen(0,base);
							game_init();
							break;
							
						//'H' turns the help menu on
						case 0x23:
							help = true;
							clear_screen(0,base);
							break;
							
						default:
							break;
					}
					//help menu
					else
						switch(last_sc)
						{
                            //when 'ESC' is pressed returns to the main menu
							case 1:
							help = false;
							break;
							
							default:
								break;
						}
						
			}
			clear_queue(apontQueue);
		}
}

/**
 * processes the movement and clicks of the mouse
 */
void process_mouse()
{
	if(!isEmptyGQueue(mouse_queue))
	{	
        //put the mouse variables with its values
		parse_mouse_event();
		//draw the mouse "sprite" and check if it doesn't go out of the screen
		process_mouse_event();
		//verifies if the mouse is inside a option sprite and treats his reaction
		check_menu_option();
	}
}

/**
 * treates the automatic movements of the game (balls and shot movements)
  */
void process_rtc()
{
	move_balls(base);
	if(fire != NULL)
	{
		delete_sprite(fire,base);
		fire->y += fire->yspeed; //increases the vertical position of the shot
		//if the shot collides with a ball
		if(check_fire_collision())
		{
            //a beep is played with the peaker
			play_song(&shot_s);
			//desactivating this flag cancells the drawing of the fire sprite
			fire_active = false;
			return;
		}
		if(fire_active)
			draw_sprite(fire,base);
	}
		
	mili_sleep(1);
}

/**
 * Screen displays the controls used in the game
 */
void help_loop()
{
     //creation, positioning and drawing the HelpKey sprite on the center of the screen
	Sprite* helpkey = create_sprite(helpk_xpm);
	helpkey->x = (HRES/2) - helpkey->width/2;
	helpkey->y = (VRES/2) - helpkey->height/2;
	draw_sprite(helpkey,base);
	drawStringAt("Esc - back to Main Menu", 5, 5, 15, 0, 2, base, table);
	
	while(help)
	{
		if(event & KBD_EVENT)
		{
			event &= ~KBD_EVENT;
			process_kbd();
		}
	}
	delete_sprite(helpkey,base);
	destroy_sprite(helpkey);
}

/**
 * Does the processment of the events that occurs during the menu
 */
void menu_loop()
{
	while(!game_on)
	{	
	

		if(help)
		{
            //Help menu Loop
			help_loop();
			clear_screen(15,base);
			//drawing of the Logo sprite
			draw_sprite(game_logo, base);
			//drawing of the New Game sprite
			draw_sprite(menu_s[0], base);
			drawStringAt("(N)ew Game", menu_s[0]->x + 5, menu_s[0]->y + 5, 15, 3, 1, base, table);
			//drawing of the NQuit sprite
			draw_sprite(menu_s[1], base);
			drawStringAt("(Q)uit", menu_s[1]->x + 15, menu_s[1]->y + 5, 15, 3, 1, base, table);
			//drawing of the Help sprite
			draw_sprite(menu_s[2], base);
			drawStringAt("(H)elp", menu_s[2]->x + 15, menu_s[2]->y + 5, 15, 3, 1, base, table);
			//drawing Best score on the screen
			drawStringAt("Best Score: ", (HRES/2)-48, 50, 0, 15, 1, base, table);
			drawIntAt(bestScore, (HRES/2)+50, 50, 0, 15,1, base, table);
		}
		else if(event & MOUSE_EVENT)
		{
			event &= ~MOUSE_EVENT;	//restoring the event value
			process_mouse();
		}	
		if(event & KBD_EVENT)
		{
			event &= ~KBD_EVENT;	//restoring the event value
			process_kbd();
		}
		
	}
}



/**the "state machine" of the program that processes all the things that occur
 * since the program is launched until its end
 */
void main_loop()
{
	
	while(true)
	{
		//prepares the design of tthe menu
		if(!game_on)
		{	
			clear_screen(15,base);
            //creation, positioning and drawing the game logo on the center of the screen
			game_logo = create_sprite(logo);
			game_logo->x = (HRES/2) - game_logo->width/2;
			game_logo->y = (VRES/2) - game_logo->height;
			draw_sprite(game_logo, base);
            //creation, positioning and drawing the New Game sprite on the left bottom of the screen
			menu_s[0] = create_sprite(not_sel_xpm);
			menu_s[0]->x = 0;
			menu_s[0]->y = (VRES) - menu_s[0]->height;
			draw_sprite(menu_s[0], base);
			drawStringAt("(N)ew Game", menu_s[0]->x + 5, menu_s[0]->y + 5, 15, 3, 1, base, table);
            //creation, positioning and drawing the Quit sprite centered on the bottom of the screen
			menu_s[1] = create_sprite(not_sel_xpm);
			menu_s[1]->x = (HRES) - menu_s[1]->width;
			menu_s[1]->y = (VRES) - menu_s[1]->height;
			draw_sprite(menu_s[1], base);
			drawStringAt("(Q)uit", menu_s[1]->x + 15, menu_s[1]->y + 5, 15, 3, 1, base, table);
            //creation, positioning and drawing the Help sprite on the right bottom of the screen
			menu_s[2] = create_sprite(not_sel_xpm);
			menu_s[2]->x = (HRES/2) - menu_s[2]->width/2;
			menu_s[2]->y = (VRES) - menu_s[2]->height;
			draw_sprite(menu_s[2], base);
			drawStringAt("(H)elp", menu_s[2]->x + 15, menu_s[2]->y + 5, 15, 3, 1, base, table);
            //drawing the Best Score centered on the top of the screen
			drawStringAt("Best Score: ", (HRES/2)-48, 50, 0, 15, 1, base, table);
			drawIntAt(bestScore, (HRES/2)+50, 50, 0, 15,1, base, table);
			//goes to the processing of the menu
			menu_loop();
			//frees the space previously allocated to the sprites
			destroy_sprite(game_logo);
			destroy_sprite(menu_s[0]);
			destroy_sprite(menu_s[1]);
			destroy_sprite(menu_s[2]);
		}
		//precessment of the game
		if(game_on)
		{
			drawStringAt("Score: ", 20, 5, 15, 0, 1, base, table);
			drawIntAt(score, 100, 5, 15, 0, 1, base, table);
			drawStringAt("                ", 600, 5, 15, 0, 1, base, table);
			drawStringAt("Time left: ", 600, 5, 15, 0, 1, base, table);
			drawIntAt(level.seconds, 700, 5, 15, 0, 1, base, table);
			
			//if there aren't any balls on the screen advances to the next level
			if(level.destroyed_balls == level.maxballs) 
			{
				change_level();
			}
			//checks if the level reaches the end (boy collides with a ball or time out)
			if(check_boy_collision()  || level.seconds == 0)
			{
                //if the last game score is bigger than the best score
                //best score is updated
				if(score > bestScore)
					bestScore = score;
				
                //creation, positioning and drawing the HGameOver sprite on the center of the screen
				Sprite *go = create_sprite(go_xpm);
				go->x = (HRES/2) - go->width/2;
				go->y = (VRES/2) - go->height;
				clear_screen(0,base);
				draw_sprite(go,base);
				//plays a gameover song
				play_song(&go_s);
				game_on = false;
				//frees the space previously allocated to the sprite
				destroy_sprite(go);
			}
			if(game_on)
			{
				//treatement of the pressed keys
				if(event & KBD_EVENT)
				{
					event &= ~KBD_EVENT;
					process_kbd();
				}
				if(!pause)
				{
					//treatement of the rtc interrupts
					if(event & RTC_EVENT)
					{
						event &= ~RTC_EVENT;
						process_rtc();
					}
					//when the player presses the shot key this event is activated
					if(event & FIRE_EVENT)
					{
						event &= ~FIRE_EVENT;		//restoring the event value
						delete_sprite(fire,base);
						shot(boy,fire,base);
						fire_active = true;
					}
				}
				else
				{
                    //these mili_sleep are here to give the blinking effect
                    //to the string "Pause" while the game is paused
					drawStringAt("Pause", 350, 5, 15, 0, 1, base, table);
					mili_sleep(1);
					drawStringAt("     ", 350, 5, 15, 0, 1, base, table);
					mili_sleep(1);
				}
			}
			//if the game ended, the program enters here
			if(!game_on)
			{
				game_finalize();
				//returns to level 1
				level.level = 1;
				//reset the score
				score = 0;
			}
		}
	}
	
}
